package fr.neyb.bernouilli.common.math.prime;

import java.nio.LongBuffer;
import java.util.Arrays;

import fr.neyb.bernouilli.common.exception.MathLimitExceededException;

public class PrimeGenerator1 {

	private static double defaultLoadFactor = 1.5d;
	private static PrimeGenerator1 mainInstance = new PrimeGenerator1();

	private final static ThreadLocal<PrimeGenerator1> instances = new ThreadLocal<PrimeGenerator1>() {
		@Override
		protected PrimeGenerator1 initialValue() {
			return mainInstance;
		}
	};

	public static PrimeGenerator1 getInstance() {
		PrimeGenerator1 result;
		if ((result = instances.get()) == null) {
			result = mainInstance;
			instances.set(result);
		}
		return result;
	}

	public static synchronized void publish() {
		if (mainInstance.nbPrimes() < getInstance().nbPrimes())
			mainInstance = getInstance();
	}

	private double loadFactor = defaultLoadFactor;

	private LongBuffer primesFound = LongBuffer.allocate(10);

	public long getNextPrime() {
		if (nbPrimes() + 1 > primesFound.capacity())
			reallocate();

		switch (primesFound.position()) {
		case 0:
			primesFound.put(2);
			return 2;
		case 1:
			primesFound.put(3);
			return 3;
		}

		long curr = primesFound.get(primesFound.position() - 1) + 2;

		int incr;
		if (curr % 3 == 0) {
			curr += 2;
			incr = 4;
		} else {
			incr = 2;
		}

		long[] prevPrimes = calculatedPrimes();

		for (;; curr += (incr = 6 - incr)) {
			boolean prime = true;

			if (curr > Integer.MAX_VALUE)
				throw new MathLimitExceededException();
			int isqrt = (int) Math.ceil(Math.sqrt((int) curr));
			// TODO extends to long (means impl sqrt for long)
			// int isqrt = MyMath.i_sqrt(curr);

			for (long currPrime : prevPrimes) {
				if (currPrime > isqrt)
					break;

				if (curr % currPrime == 0) {
					prime = false;
					break;
				}
			}

			if (prime) {
				primesFound.put(curr);
				return curr;
			}
		}
	}

	public long get(int i) {
		while (i + 1 > nbPrimes()) {
			getNextPrime();
		}
		return primesFound.get(i);
	}

	public long getLastPrime() {
		return primesFound.get(primesFound.position() - 1);
	}

	public long[] calculatedPrimes() {
		// TODO check primesFound.position() or primesFound.position() +
		// primesFound.arrayOffset()
		return Arrays.copyOfRange(primesFound.array(),
				primesFound.arrayOffset(), primesFound.position());
	}

	private void reallocate() {
		int newCapacity = (int) Math.floor(loadFactor * nbPrimes());

		if (newCapacity > primesFound.capacity()) {
			long[] primes = primesFound.array();
			int offset = primesFound.arrayOffset();
			int pos = primesFound.position();

			primesFound = LongBuffer.allocate(newCapacity);
			primesFound.put(primes, offset, pos);
		}
	}

	public int nbPrimes() {
		return primesFound.position();
	}
}
